home *** CD-ROM | disk | FTP | other *** search
/ Komputer for Alle 2001 #11 / CD 11 (Black) - 2001.iso / FAVORG / FAVO_SRC.ZIP / StringExt.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2000-11-29  |  17.2 KB  |  714 lines

  1. // FAVORG Version 1.1
  2. // Copyright (c) 2000 Ziff Davis Media, Inc.
  3. // All rights reserved.
  4. // First Published in PC Magazine, US Edition, November 7, 2000.
  5. // Programmer: Patrick Philippot
  6.  
  7. ////////////////////////////////////////////////////////////
  8. // Comments
  9. //
  10. // This class, like the CString class, is not designed to 
  11. // allow easy derivation. It merely adds useful new member 
  12. // functions and doesn't try to modify the basic behavior 
  13. // of CString. CStringEx is not dependent from any other 
  14. // classes. Therefore it can be used for itself. Like in C
  15. // String, no member function is virtual with exception 
  16. // of the destructor.
  17. //
  18. // CStringEx doesn't manipulate the CStringData structure or
  19. // the m_pchData data member directly. All its member functions 
  20. // use either CString member functions or C++/Win32 APIs. CStringEx
  21. // doesn't rely on any undocumented CString feature.
  22. //
  23. // Unless the function returns a specific value, all these
  24. // functions return a reference to the CStringEx itself (although
  25. // this is less useful than with standard strxxx functions, there
  26. // are some situations where this may help). The only exceptions
  27. // are the path manipulation functions which are static and can
  28. // therefore be called to manipulate a path without instantiating
  29. // a CStringEx.
  30. //
  31. // Exceptions: since none of these additional functions allocates
  32. // memory by itself, all exceptions will be thrown by the CString 
  33. // class.
  34.  
  35.  
  36. // Includes
  37. #include "stdafx.h"
  38.  
  39. #include "stringext.h"
  40. #include <tchar.h>
  41.  
  42. #define new DEBUG_NEW
  43.  
  44. //====== Constructors ======
  45.  
  46. CStringEx::CStringEx()
  47. {
  48. }
  49.  
  50. //====== Operations ======
  51.  
  52. // See Inlines in cstringext.h
  53.  
  54. CStringEx CStringEx::ExpandTabs(int nTabIncr, BOOL bIgnoreQuotes, int nMaxSize)
  55. // Expands tab characters according to a given tab stop increment.
  56. // nMaxSize defaults to INT_MAX.
  57. {
  58.     int        nNumSpaces;        // Number of spaces to add when a tab is found
  59.     BOOL    bInQuotes;        // Doesn't expand tabs between quotes if requested
  60.     TCHAR    c;                // Current source char
  61.     int        nSourceIndex;    // Index in source
  62.     int        nTargetIndex;    // Index in target
  63.     int        nAllocLen;        // Starting size of target buffer
  64.     int        nSrcLen = GetLength();
  65.     TCHAR    chQuote;        // Current quote character
  66.  
  67.     nAllocLen = nSrcLen * 2;    // We start with a target buffer being twice
  68.                                 // the size of *this* string
  69.     CStringEx sTarget(' ', nAllocLen);
  70.  
  71.     if (nSrcLen == 0)    // empty
  72.         return(sTarget);
  73.  
  74.     // Initialize variables
  75.     c = GetAt(0);
  76.     nSourceIndex = 0;
  77.     nTargetIndex = 0;
  78.     nMaxSize--;
  79.     bInQuotes = FALSE;
  80.  
  81.     // for each character in source (this string)...
  82.     while ((nSourceIndex < nSrcLen) && (nTargetIndex < nMaxSize))
  83.     {
  84.         switch (c)
  85.         {
  86.             case '\'':
  87.             case '"' :
  88.                 if (!bIgnoreQuotes)
  89.                 {
  90.                     if (!bInQuotes)
  91.                     {
  92.                         bInQuotes = TRUE;
  93.                         chQuote = c;
  94.                     }
  95.                     else if ((c == chQuote) 
  96.                              &&
  97.                              (GetAt(nSourceIndex - 1) != '\\'))
  98.                     // Closing, non escaped quote
  99.                         bInQuotes = FALSE;
  100.                 }
  101.                 goto default_processing;
  102.                 // break;                                  
  103.  
  104.             case '\t':    // Got a tab
  105.                 if (!bInQuotes)
  106.                 {
  107.                     // Compute the number of spaces necessary to go to the next tab stop
  108.                     nNumSpaces = nTabIncr - (nTargetIndex % nTabIncr);
  109.                     if ((nTabIncr > 0) && (nNumSpaces != 0))
  110.                     {
  111.                         if (nTargetIndex + nNumSpaces < nMaxSize)
  112.                         {
  113.                             // Append spaces to target
  114.                             LPSTR pBuf = sTarget.GetBuffer(nTargetIndex + nNumSpaces);
  115.                             _tcsncset(pBuf + nTargetIndex, ' ', nNumSpaces);
  116.                             sTarget.ReleaseBuffer();
  117.                             nTargetIndex += nNumSpaces;        // Skip spaces
  118.                         }
  119.                     }
  120.                     break;
  121.                 }
  122.                 // else fall through to default procesing
  123.  
  124. default_processing:
  125.  
  126.             default:
  127.                 if (nTargetIndex >= nMaxSize)                // Overflow: return unchanged string
  128.                     return(*this);
  129.                 if (nTargetIndex >= sTarget.GetLength())    // Grow target
  130.                     sTarget += CStringEx(' ', nAllocLen);    // Store char
  131.                 sTarget.SetAt(nTargetIndex++, c);
  132.                 break;
  133.         }
  134.         if (++nSourceIndex < nSrcLen)
  135.             c = GetAt(nSourceIndex);    // Get next char from source (this string)
  136.     } // switch (c)
  137.  
  138.     if ((nSourceIndex < nSrcLen) && (nTargetIndex >= nMaxSize))
  139.     // Abnormal termination. Should not happen.
  140.         return(*this);
  141.     else
  142.     {
  143.         // Return a clean string with no extra memory
  144.         sTarget.GetBufferSetLength(nTargetIndex);
  145.         sTarget.ReleaseBuffer(-1);
  146.         return(sTarget);
  147.     }
  148. }
  149.  
  150. CStringEx CStringEx::CompressSpaces(int nTabIncr, BOOL bIgnoreQuotes)
  151. // Compress space characters to tabs according to the value of the
  152. // nTabIncr parameter. Spaces within quotes are optionally ignored.
  153. {
  154.  
  155.     TCHAR        c;
  156.     int            nCol;
  157.     int            nNumSpaces;
  158.     TCHAR*        lpszSrc;
  159.     TCHAR*        lpszDest;
  160.     TCHAR*        lpszTarget;
  161.     BOOL        bInQuotes;
  162.  
  163.     int            nLen = GetLength();
  164.  
  165.     lpszSrc = GetBuffer(nLen + 1);
  166.     // Allocate space for target string
  167.     lpszDest = (TCHAR*) new    TCHAR[nLen + 1];
  168.     lpszTarget = lpszDest;
  169.  
  170.     // If there's something to do...
  171.     if ((nLen >= nTabIncr) && (_tcschr(lpszSrc, ' ') != NULL))
  172.     {
  173.         nCol = 0;
  174.         nNumSpaces = 0;
  175.         bInQuotes = FALSE;
  176.         do
  177.         {
  178.             c = *lpszSrc;        // Get next character in source
  179.             switch (c)
  180.             {
  181.                 // Special processing for ' and "
  182.                 // Doesn't compress spaces inside quotes if !bIgnoreQuotes
  183.                 case '"':
  184.                 case '\'':
  185.                     if (bIgnoreQuotes)
  186.                         goto DefaultProcessing;
  187.  
  188.                     if (!bInQuotes)
  189.                     {
  190.                         nCol++;
  191.                         memset(lpszDest, ' ', nNumSpaces);
  192.                         lpszDest += nNumSpaces;
  193.                         bInQuotes = TRUE;
  194.                     }
  195.                     else if ((c == '\'') && (*(lpszSrc + 1) == '\''))
  196.                     {
  197.                         nNumSpaces = 0;
  198.                         *lpszDest = '\''; // '' sequence. Insert it and skip second '
  199.                         lpszDest++;
  200.                         *lpszDest = '\'';
  201.                         lpszDest++;
  202.                         lpszSrc++;
  203.                         goto NextChar;
  204.                     }
  205.                     else if (((c == '"') && (*(lpszSrc - 1) != '\\')) || (c == '\''))
  206.                     {
  207.                         nCol++;
  208.                         bInQuotes = FALSE;
  209.                     }
  210.  
  211.                     *lpszDest = c;
  212.                     lpszDest++;
  213.                     nNumSpaces = 0;
  214.                     break;
  215.  
  216.                 case ' ':
  217.                     if (!bInQuotes)
  218.                     {
  219.                         nNumSpaces++;
  220.                         nCol++;
  221.                         if ((nTabIncr <= 0) || (nCol % nTabIncr == 0))
  222.                         {
  223.                             if (nNumSpaces > 1)
  224.                                 *lpszDest = '\t';
  225.                             else
  226.                                 *lpszDest = ' ';
  227.                             nNumSpaces = 0;
  228.                             lpszDest++;
  229.                         }
  230.                     }
  231.                     else
  232.                     {
  233.                         nNumSpaces = 0;
  234.                         *lpszDest = ' ';
  235.                         lpszDest++;
  236.                     }
  237.                     break;
  238.  
  239.                 case '\t':
  240.                     nCol = 0;
  241.                     nNumSpaces = 0;
  242.                     *lpszDest = '\t';
  243.                     lpszDest++;
  244.                     break;
  245.  
  246.                 default:
  247. DefaultProcessing:
  248.                     nCol++;
  249.                     memset(lpszDest, ' ', nNumSpaces);
  250.                     lpszDest += nNumSpaces;
  251.                     nNumSpaces = 0;
  252.                     *lpszDest = c;
  253.                     lpszDest++;
  254.                     break;
  255.             }
  256. NextChar:
  257.             lpszSrc++;
  258.         }
  259.         while (c != '\0');
  260.     }
  261.     ReleaseBuffer(-1);
  262.  
  263.     CStringEx sTarget(lpszTarget);
  264.     delete [] lpszTarget;
  265.  
  266.     return sTarget;
  267. }
  268.  
  269. CStringEx& CStringEx::Insert(const CString sSource, int nPos)
  270. // Insert the source string at the given position.
  271. // If specified position is past EOL, overlay string instead.
  272. {
  273.     int nLen;
  274.  
  275.     if ((nLen = sSource.GetLength()) == 0)
  276.         return (*this);
  277.     if (nPos < GetLength())
  278.     {
  279.         LPSTR pBuf = MakeRoom(nPos, nLen);
  280.         memcpy(pBuf, (LPCTSTR) sSource, nLen);
  281.         ReleaseBuffer();
  282.         return(*this);
  283.     }
  284.     else
  285.         return(Overlay(sSource, nPos));
  286. }
  287.  
  288. CStringEx& CStringEx::InsertChar(const TCHAR ch, int nPos)
  289. // Insert character at the given position
  290. // If specified position is past EOL, overlay char instead.
  291. {
  292.     if (nPos < GetLength())
  293.     {
  294.         LPTSTR pBuf = MakeRoom(nPos, 1);
  295.         pBuf[0] = ch;
  296.         ReleaseBuffer();
  297.         return(*this);
  298.     }
  299.     else
  300.         return(OverlayChar(ch, nPos));
  301. }
  302.  
  303. CStringEx& CStringEx::PadRight(const TCHAR ch, int nNewLen)
  304. // Pad string with trailing chars so that the length becomes nNewLen
  305. {
  306.     int nLen = GetLength();
  307.     if (nNewLen > nLen)
  308.         *this += CString(ch, nNewLen - nLen);
  309.     return(*this);
  310. }
  311.  
  312. CStringEx& CStringEx::PadLeft(const TCHAR ch, int nNewLen)
  313. // Pad string with leading chars so that the length becomes nNewLen
  314. {
  315.     int nLen = GetLength();
  316.     if (nNewLen > nLen)
  317.         Insert(CString(ch, nNewLen - nLen), 0);
  318.     return(*this);
  319. }
  320.  
  321. CStringEx& CStringEx::Overlay(const CString sSource, int nPos)
  322. // Overlay sSource at specified position. Pad with spaces if necessary.
  323. {
  324.     int nLen = GetLength();
  325.     LPTSTR pBuf;
  326.     int nSrcLen = sSource.GetLength();
  327.  
  328.     if (nPos > nLen)
  329.         PadRight(' ', nPos);
  330.     if (nPos + nSrcLen > nLen)
  331.         pBuf = GetBufferSetLength(nPos + nSrcLen);        // String must grow
  332.     else
  333.         pBuf = GetBuffer(nLen);                            // Source fit into target
  334.     memcpy(pBuf + nPos, (LPCTSTR) sSource, nSrcLen);
  335.     ReleaseBuffer(-1);
  336.     return(*this);
  337. }
  338.  
  339. CStringEx& CStringEx::OverlayChar(const TCHAR ch, int nPos)
  340. // Overlay character at specified position. Pad with spaces if necessary.
  341. {
  342.     if (nPos >= GetLength())
  343.         PadRight(' ', nPos + 1);
  344.     SetAt(nPos, ch);
  345.     return(*this);
  346. }
  347.  
  348. CString CStringEx::FullPath(CString sRelPath)
  349. // static function encapsulating the _tfullpath function
  350. {
  351.     CString sTemp;
  352.  
  353.     _tfullpath( sTemp.GetBuffer(_MAX_PATH), sRelPath, _MAX_PATH);
  354.     sTemp.ReleaseBuffer();
  355.  
  356.     return(sTemp);
  357. }
  358.  
  359. void CStringEx::SplitPath(CString const sPath, CString& sDrive, CString& sDir, CString& sName, CString& sExt, BOOL bIsDir)
  360. // static function encapsulating the _tsplitpath function
  361. {
  362.     _tsplitpath((LPCTSTR) sPath,
  363.                 sDrive.GetBuffer(_MAX_DRIVE),
  364.                 sDir.GetBuffer(_MAX_DIR), 
  365.                 sName.GetBuffer(_MAX_FNAME), 
  366.                 sExt.GetBuffer(_MAX_EXT));
  367.     sDrive.ReleaseBuffer(-1);
  368.     sDir.ReleaseBuffer(-1);
  369.     sName.ReleaseBuffer(-1);
  370.     sExt.ReleaseBuffer(-1);
  371.  
  372.     if (bIsDir)
  373.         sName += sExt;
  374. }
  375.  
  376. CString CStringEx::MakePath(CString sDrive, CString sDir, CString sName, CString sExt)
  377. // static function encapsulating the _tmakepath function
  378. {
  379.     CString sTemp;
  380.  
  381.     _tmakepath( sTemp.GetBuffer(_MAX_PATH), 
  382.                 (LPCTSTR) sDrive,
  383.                 (LPCTSTR) sDir,
  384.                 (LPCTSTR) sName,
  385.                 (LPCTSTR) sExt);
  386.     sTemp.ReleaseBuffer(-1);
  387.     return(sTemp);
  388. }
  389.  
  390. CStringEx& CStringEx::ReplaceNameComponent(const CString sNewComponent, int nComponent)
  391. // Assumes current string is a path. Replace specified name component.
  392. {
  393.     CString    sDrive;
  394.     CString sDir;
  395.     CString    sName;
  396.     CString sExt;
  397.     CString    sPath;
  398.  
  399.     ASSERT ((nComponent == EX_STRING_REPDRIVE) 
  400.             || (nComponent == EX_STRING_REPDIR) 
  401.             || (nComponent == EX_STRING_REPNAME) 
  402.             || (nComponent == EX_STRING_REPEXT));
  403.  
  404.     // First retrieve current filename components...
  405.     SplitPath(*this, sDrive, sDir, sName, sExt);
  406.  
  407.     // then rebuild the filename using the new component
  408.     switch (nComponent)
  409.     {
  410.         case EX_STRING_REPDRIVE:
  411.             *this = MakePath(sNewComponent, sDir, sName, sExt);
  412.             break;
  413.         case EX_STRING_REPDIR:
  414.             *this = MakePath(sDrive, sNewComponent, sName, sExt);
  415.             break;
  416.         case EX_STRING_REPNAME:
  417.             *this = MakePath(sDrive, sDir, sNewComponent, sExt);
  418.             break;
  419.         case EX_STRING_REPEXT:
  420.             *this = MakePath(sDrive, sDir, sName, sNewComponent);
  421.             break;
  422.     }
  423.     
  424.     return(*this);
  425. }
  426.  
  427. CString CStringEx::GetExtension()
  428. {
  429.     CString sExt;
  430.  
  431.     int nDotPos = ReverseFind('.');
  432.     if (nDotPos != -1)
  433.         sExt = Mid(nDotPos);
  434.  
  435.     return(sExt);
  436. }
  437.  
  438. int CStringEx::FindNoCase(LPCTSTR lpszSub)
  439. {
  440.     CStringEx sThis = *this;
  441.     CStringEx sTarget = lpszSub;
  442.  
  443.     sThis.MakeUpper();
  444.     sTarget.MakeUpper();
  445.  
  446.     return(sThis.Find(sTarget));
  447. }
  448.  
  449. CStringEx& CStringEx::Replace(CString sToken, CString sNewToken, int nMode, BOOL bCase)
  450. // Replace a token with another. Can replace first, last or all tokens.
  451. {
  452.     int            nPos;
  453.     BOOL        bReversed;            
  454.     int            nTokenLen = sToken.GetLength();
  455.     CString&    sTokenRef = sToken;            // Different references will be used
  456.     CStringEx&    sSourceRef = *this;            // if we are doing a Replace Last
  457.     CString&    sNewTokenRef = sNewToken;
  458.     CString        sTokenRev;
  459.     CString        sSourceRev;
  460.     CString        sNewTokenRev;
  461.  
  462.     ASSERT ((nMode == EX_STRING_REPFIRST) || (nMode == EX_STRING_REPLAST) || (nMode == EX_STRING_REPALL));
  463.  
  464.     if ((sToken.GetLength() == 0) || (GetLength() == 0))
  465.         return(*this);
  466.  
  467.     bReversed = nMode == EX_STRING_REPLAST;
  468.     if (bReversed)
  469.     {
  470.         nMode = EX_STRING_REPFIRST;        // Will proceed like "Replace First..."
  471.         sTokenRev = sToken;
  472.         sTokenRev.MakeReverse();        // but will reverse all strings before.
  473.         sTokenRef = sTokenRev;                
  474.         sNewTokenRev = sNewToken;        // We work on copies anyway. 
  475.         sNewTokenRev.MakeReverse();        
  476.         sNewTokenRef = sNewTokenRev;        
  477.         sSourceRev = *this;                
  478.         sSourceRev.MakeReverse();
  479.         sSourceRef = sSourceRev;
  480.     }
  481.  
  482.     // Find first occurence of sToken...
  483.     nPos = bCase ? sSourceRef.Find(sTokenRef) : sSourceRef.FindNoCase(sTokenRef);
  484.     while (nPos != -1)
  485.     {
  486.         // Delete and replace it
  487.         sSourceRef.Delete(nPos, nTokenLen);
  488.         sSourceRef.Insert(sNewTokenRef, nPos);
  489.         // Get next occurence - Replace only one occurence if Replace All not specified
  490.         nPos = (nMode == EX_STRING_REPALL) ? (bCase ? sSourceRef.Find(sTokenRef) : sSourceRef.FindNoCase(sTokenRef)) : -1;
  491.     };
  492.  
  493.     if (bReversed)
  494.     {
  495.         sSourceRef.MakeReverse();
  496.         *this = sSourceRef;
  497.     }
  498.  
  499.     return(*this);
  500. }
  501.  
  502. CStringEx& CStringEx::StripSlash(void)
  503. // Removes trailing slash if any but not if the string represents a root directory
  504. {
  505.     int nLen = GetLength();
  506.     if ((nLen > 0) && (GetAt(nLen - 1) == '\\') && (!((nLen == 3) && (GetAt(1) == ':'))))
  507.         SetAt(nLen - 1, '\0');
  508.  
  509.     return (*this);
  510. }
  511.  
  512. CString& CStringEx::StripToPath()
  513. {
  514.     int nSlashPos = ReverseFind('\\');
  515.     if (nSlashPos != -1)
  516.         Delete(nSlashPos + 1);
  517.  
  518.     return (*this);
  519. }
  520.  
  521. CStringEx CStringEx::GetFirstToken(CString& sDelimiters, int nMaxLen)
  522. // Please be aware that the string **is** modified by each call to
  523. // _tcstok (null chars inserted). So using a copy of the string
  524. // is certainly a good choice.
  525.  
  526. // The first separator will be translated to a special character
  527. // '\1' before parsing when found within quotes. It is then translated
  528. // to sDelimiters[0] when returning the token to the caller. This will 
  529. // also happen for GetNextToken even if the delimiters have changed.
  530. {
  531.  
  532.  
  533.     CString sToken;
  534.     char*    p;
  535.     BOOL    bInQuotes = FALSE;
  536.     int        nLen = GetLength();
  537.  
  538.     // Protect first separator if within quotes
  539.     for (int nIndex = 0; nIndex < nLen; nIndex++)
  540.     {
  541.         if (GetAt(nIndex) == '"')
  542.             bInQuotes = !bInQuotes;
  543.         else if (bInQuotes && (GetAt(nIndex) == sDelimiters[0]))
  544.             SetAt(nIndex, '\1');
  545.     }
  546.  
  547.     if ((p = _tcstok(GetBuffer(nMaxLen), sDelimiters)) != NULL)
  548.     {
  549.         sToken = p;
  550.  
  551.         BOOL bInQuotes = FALSE;
  552.         int    nLen = sToken.GetLength();
  553.         // Restore original characters before returning
  554.         for (int nIndex = 0; nIndex < nLen; nIndex++)
  555.         {
  556.             if (sToken.GetAt(nIndex) == '"')
  557.                 bInQuotes = !bInQuotes;
  558.             else if (bInQuotes && sToken.GetAt(nIndex) == '\1')
  559.                 sToken.SetAt(nIndex, sDelimiters[0]);
  560.         }
  561.     }
  562.  
  563.     return(sToken);        
  564. }
  565.  
  566. CStringEx CStringEx::GetNextToken(CString& sDelimiters)
  567. // Please be aware that the string **is** modified by each call to
  568. // _tcstok (null chars inserted). So using a copy of the string
  569. // is certainly a good choice.
  570. {
  571.     CString sToken;
  572.     char*    p;
  573.  
  574.     if ((p = _tcstok(NULL, sDelimiters)) != NULL)
  575.     {
  576.         sToken = p;
  577.  
  578.         BOOL bInQuotes = FALSE;
  579.         int    nLen = sToken.GetLength();
  580.         // Restore original characters before returning
  581.         for (int nIndex = 0; nIndex < nLen; nIndex++)
  582.         {
  583.             if (sToken.GetAt(nIndex) == '"')
  584.                 bInQuotes = !bInQuotes;
  585.             else if (bInQuotes && sToken.GetAt(nIndex) == '\1')
  586.                 sToken.SetAt(nIndex, sDelimiters[0]);
  587.         }
  588.     }
  589.     else
  590.         ReleaseBuffer(-1);
  591.  
  592.     return(sToken);        
  593. }
  594.  
  595. CStringEx& CStringEx::RemoveQuotes()
  596. {
  597.     if ((GetLength() >= 2) && (GetAt(0) == '"') && (GetAt(GetLength() - 1) == '"'))
  598.     {
  599.         Delete(0, 1);
  600.         Delete(GetLength() - 1, 1);
  601.     }
  602.  
  603.     return (*this);
  604. }
  605.  
  606. CStringEx& CStringEx::ItoA(int value, int radix)
  607. {
  608.     TCHAR szBuf[20];
  609.  
  610.     _itot(value, szBuf, radix);
  611.     *this = szBuf;
  612.     
  613.     return(*this);        
  614. }
  615.  
  616. CStringEx& CStringEx::LtoA(int value, CString sNum, int radix)
  617. {
  618.     TCHAR szBuf[20];
  619.  
  620.     _ltot(value, szBuf, radix);
  621.     *this = szBuf;
  622.         
  623.     return(*this);        
  624. }
  625.  
  626. long CStringEx::AtoL()
  627. {
  628.     long lResult;
  629.     
  630.     lResult = _tcstol(GetBuffer(GetLength() + 1), NULL, 10);
  631.     ReleaseBuffer(-1);
  632.     
  633.     return(lResult);
  634. }
  635.  
  636. double CStringEx::AtoD()
  637. {
  638.     double dResult;
  639.     
  640.     dResult = _tcstod(GetBuffer(GetLength() + 1), NULL);
  641.     ReleaseBuffer(-1);
  642.         
  643.     return(dResult);        
  644. }
  645.  
  646. CStringEx CStringEx::URLDecode()
  647. {
  648.     CStringEx sDecoded;
  649.  
  650.     int nIndex = 0;
  651.     while (nIndex < GetLength())
  652.     {
  653.         char chCur = GetAt(nIndex);
  654.  
  655.         if (chCur == '+')
  656.             sDecoded += ' ';
  657.         else if (chCur == '%')
  658.         {
  659.             CString sValue;
  660.             
  661.             if (IsHexDigit(nIndex + 1))
  662.             {
  663.                 nIndex++;
  664.                 sValue += GetAt(nIndex);
  665.                 if (IsHexDigit(nIndex + 1))
  666.                 {
  667.                     nIndex++;
  668.                     sValue += GetAt(nIndex);
  669.                 }
  670.                 else
  671.                 {
  672.                     sDecoded += chCur + sValue;
  673.                     goto nextchar;
  674.                 }
  675.  
  676.                 int nCode;
  677.                 sscanf(sValue, "%x", &nCode);
  678.                 sDecoded += (char) nCode;
  679.             }
  680.             else
  681.                 sDecoded += chCur;
  682.         }
  683.         else
  684.             sDecoded += chCur;
  685. nextchar:
  686.         nIndex++;
  687.     }
  688.  
  689.     return sDecoded;
  690. }
  691.  
  692. //====== Overridables ======
  693.  
  694.         
  695. //====== Implementation ======
  696.  
  697. CStringEx::~CStringEx()
  698. {
  699. }
  700.  
  701. LPTSTR CStringEx::MakeRoom(int nPos, int nExtra)
  702. // Insert nExtra character slots at nPos and returns
  703. // a pointer to the first inserted slot. ReleaseBuffer
  704. // must be invoked by the caller.
  705. {
  706.     int nLen = GetLength();
  707.  
  708.     ASSERT(nPos < nLen);
  709.  
  710.     LPTSTR pBuf = GetBuffer(nLen + nExtra) + nPos;
  711.     memmove(pBuf + nExtra, pBuf, nLen + 1 - nPos);
  712.  
  713.     return(pBuf);
  714. }